/*
 * Copyright (c) 2001-2004 Caucho Technology, Inc.  All rights reserved.
 *
 * The Apache Software License, Version 1.1
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution, if
 *    any, must include the following acknowlegement:
 *       "This product includes software developed by the
 *        Caucho Technology (http://www.caucho.com/)."
 *    Alternately, this acknowlegement may appear in the software itself,
 *    if and wherever such third-party acknowlegements normally appear.
 *
 * 4. The names "Hessian", "Resin", and "Caucho" must not be used to
 *    endorse or promote products derived from this software without prior
 *    written permission. For written permission, please contact
 *    info@caucho.com.
 *
 * 5. Products derived from this software may not be called "Resin"
 *    nor may "Resin" appear in their names without prior written
 *    permission of Caucho Technology.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL CAUCHO TECHNOLOGY OR ITS CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
 * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
 * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
 * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * @author Scott Ferguson
 */
package net.hasor.libs.com.caucho.hessian.io;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
/**
 * Input stream for Hessian requests.
 *
 * <p>HessianInput is unbuffered, so any client needs to provide
 * its own buffering.
 *
 * <pre>
 * InputStream is = ...; // from http connection
 * HessianInput in = new HessianInput(is);
 * String value;
 *
 * in.startReply();         // read reply header
 * value = in.readString(); // read string value
 * in.completeReply();      // read reply footer
 * </pre>
 */
@SuppressWarnings("unused")
public class HessianInput extends AbstractHessianInput {
    private static int          END_OF_DATA = -2;
    private static Field        _detailMessageField;
    // factory for deserializing objects in the input stream
    protected SerializerFactory _serializerFactory;
    protected ArrayList<Object> _refs;
    // the underlying input stream
    private InputStream         _is;
    // a peek character
    protected int               _peek       = -1;
    // the method for a call
    private String              _method;
    private Reader              _chunkReader;
    private InputStream         _chunkInputStream;
    private Throwable           _replyFault;
    private StringBuffer        _sbuf       = new StringBuffer();
    // true if this is the last chunk
    private boolean             _isLastChunk;
    // the chunk length
    private int                 _chunkLength;
    /**
     * Creates an uninitialized Hessian input stream.
     */
    public HessianInput() {}
    /**
     * Creates a new Hessian input stream, initialized with an
     * underlying input stream.
     *
     * @param is the underlying input stream.
     */
    public HessianInput(InputStream is) {
        init(is);
    }
    /**
     * Sets the serializer factory.
     */
    public void setSerializerFactory(SerializerFactory factory) {
        _serializerFactory = factory;
    }
    /**
     * Gets the serializer factory.
     */
    public SerializerFactory getSerializerFactory() {
        return _serializerFactory;
    }
    /**
     * Initialize the hessian stream with the underlying input stream.
     */
    public void init(InputStream is) {
        _is = is;
        _method = null;
        _isLastChunk = true;
        _chunkLength = 0;
        _peek = -1;
        _refs = null;
        _replyFault = null;
        if (_serializerFactory == null)
            _serializerFactory = new SerializerFactory();
    }
    /**
     * Returns the calls method
     */
    public String getMethod() {
        return _method;
    }
    /**
     * Returns any reply fault.
     */
    public Throwable getReplyFault() {
        return _replyFault;
    }
    /**
     * Starts reading the call
     *
     * <pre>
     * c major minor
     * </pre>
     */
    public int readCall() throws IOException {
        int tag = read();
        if (tag != 'c')
            throw error("expected hessian call ('c') at " + codeName(tag));
        int major = read();
        int minor = read();
        return (major << 16) + minor;
    }
    /**
     * For backward compatibility with HessianSkeleton
     */
    public void skipOptionalCall() throws IOException {
        int tag = read();
        if (tag == 'c') {
            read();
            read();
        } else
            _peek = tag;
    }
    /**
     * Starts reading the call
     *
     * <p>A successful completion will have a single value:
     *
     * <pre>
     * m b16 b8 method
     * </pre>
     */
    public String readMethod() throws IOException {
        int tag = read();
        if (tag != 'm')
            throw error("expected hessian method ('m') at " + codeName(tag));
        int d1 = read();
        int d2 = read();
        _isLastChunk = true;
        _chunkLength = d1 * 256 + d2;
        _sbuf.setLength(0);
        int ch;
        while ((ch = parseChar()) >= 0)
            _sbuf.append((char) ch);
        _method = _sbuf.toString();
        return _method;
    }
    /**
     * Starts reading the call, including the headers.
     *
     * <p>The call expects the following protocol data
     *
     * <pre>
     * c major minor
     * m b16 b8 method
     * </pre>
     */
    public void startCall() throws IOException {
        readCall();
        while (readHeader() != null) {
            readObject();
        }
        readMethod();
    }
    /**
     * Completes reading the call
     *
     * <p>A successful completion will have a single value:
     *
     * <pre>
     * z
     * </pre>
     */
    public void completeCall() throws IOException {
        int tag = read();
        if (tag == 'z') {} else
            throw error("expected end of call ('z') at " + codeName(tag) + ".  Check method arguments and ensure method overloading is enabled if necessary");
    }
    /**
     * Reads a reply as an object.
     * If the reply has a fault, throws the exception.
     */
    public Object readReply(Class<?> expectedClass) throws Throwable {
        int tag = read();
        if (tag != 'r')
            error("expected hessian reply at " + codeName(tag));
        int major = read();
        int minor = read();
        tag = read();
        if (tag == 'f')
            throw prepareFault();
        else {
            _peek = tag;
            Object value = readObject(expectedClass);
            completeValueReply();
            return value;
        }
    }
    /**
     * Starts reading the reply
     *
     * <p>A successful completion will have a single value:
     *
     * <pre>
     * r
     * </pre>
     */
    public void startReply() throws Throwable {
        int tag = read();
        if (tag != 'r')
            error("expected hessian reply at " + codeName(tag));
        int major = read();
        int minor = read();
        startReplyBody();
    }
    public void startReplyBody() throws Throwable {
        int tag = read();
        if (tag == 'f')
            throw prepareFault();
        else
            _peek = tag;
    }
    /**
     * Prepares the fault.
     */
    private Throwable prepareFault() throws IOException {
        HashMap<Object, Object> fault = readFault();
        Object detail = fault.get("detail");
        String message = (String) fault.get("message");
        if (detail instanceof Throwable) {
            _replyFault = (Throwable) detail;
            if (message != null && _detailMessageField != null) {
                try {
                    _detailMessageField.set(_replyFault, message);
                } catch (Throwable e) {}
            }
            return _replyFault;
        } else {
            String code = (String) fault.get("code");
            _replyFault = new HessianServiceException(message, code, detail);
            return _replyFault;
        }
    }
    /**
     * Completes reading the call
     *
     * <p>A successful completion will have a single value:
     *
     * <pre>
     * z
     * </pre>
     */
    public void completeReply() throws IOException {
        int tag = read();
        if (tag != 'z')
            error("expected end of reply at " + codeName(tag));
    }
    /**
     * Completes reading the call
     *
     * <p>A successful completion will have a single value:
     *
     * <pre>
     * z
     * </pre>
     */
    public void completeValueReply() throws IOException {
        int tag = read();
        if (tag != 'z')
            error("expected end of reply at " + codeName(tag));
    }
    /**
     * Reads a header, returning null if there are no headers.
     *
     * <pre>
     * H b16 b8 value
     * </pre>
     */
    public String readHeader() throws IOException {
        int tag = read();
        if (tag == 'H') {
            _isLastChunk = true;
            _chunkLength = (read() << 8) + read();
            _sbuf.setLength(0);
            int ch;
            while ((ch = parseChar()) >= 0)
                _sbuf.append((char) ch);
            return _sbuf.toString();
        }
        _peek = tag;
        return null;
    }
    /**
     * Reads a null
     *
     * <pre>
     * N
     * </pre>
     */
    public void readNull() throws IOException {
        int tag = read();
        switch (tag) {
        case 'N':
            return;
        default:
            throw expect("null", tag);
        }
    }
    /**
     * Reads a boolean
     *
     * <pre>
     * T
     * F
     * </pre>
     */
    public boolean readBoolean() throws IOException {
        int tag = read();
        switch (tag) {
        case 'T':
            return true;
        case 'F':
            return false;
        case 'I':
            return parseInt() == 0;
        case 'L':
            return parseLong() == 0;
        case 'D':
            return parseDouble() == 0.0;
        case 'N':
            return false;
        default:
            throw expect("boolean", tag);
        }
    }
    /**
     * Reads a byte
     *
     * <pre>
     * I b32 b24 b16 b8
     * </pre>
     */
    /*
    public byte readByte()
      throws IOException
    {
      return (byte) readInt();
    }
    */
    /**
     * Reads a short
     *
     * <pre>
     * I b32 b24 b16 b8
     * </pre>
     */
    public short readShort() throws IOException {
        return (short) readInt();
    }
    /**
     * Reads an integer
     *
     * <pre>
     * I b32 b24 b16 b8
     * </pre>
     */
    public int readInt() throws IOException {
        int tag = read();
        switch (tag) {
        case 'T':
            return 1;
        case 'F':
            return 0;
        case 'I':
            return parseInt();
        case 'L':
            return (int) parseLong();
        case 'D':
            return (int) parseDouble();
        default:
            throw expect("int", tag);
        }
    }
    /**
     * Reads a long
     *
     * <pre>
     * L b64 b56 b48 b40 b32 b24 b16 b8
     * </pre>
     */
    public long readLong() throws IOException {
        int tag = read();
        switch (tag) {
        case 'T':
            return 1;
        case 'F':
            return 0;
        case 'I':
            return parseInt();
        case 'L':
            return parseLong();
        case 'D':
            return (long) parseDouble();
        default:
            throw expect("long", tag);
        }
    }
    /**
     * Reads a float
     *
     * <pre>
     * D b64 b56 b48 b40 b32 b24 b16 b8
     * </pre>
     */
    public float readFloat() throws IOException {
        return (float) readDouble();
    }
    /**
     * Reads a double
     *
     * <pre>
     * D b64 b56 b48 b40 b32 b24 b16 b8
     * </pre>
     */
    public double readDouble() throws IOException {
        int tag = read();
        switch (tag) {
        case 'T':
            return 1;
        case 'F':
            return 0;
        case 'I':
            return parseInt();
        case 'L':
            return (double) parseLong();
        case 'D':
            return parseDouble();
        default:
            throw expect("long", tag);
        }
    }
    /**
     * Reads a date.
     *
     * <pre>
     * T b64 b56 b48 b40 b32 b24 b16 b8
     * </pre>
     */
    public long readUTCDate() throws IOException {
        int tag = read();
        if (tag != 'd')
            throw error("expected date at " + codeName(tag));
        long b64 = read();
        long b56 = read();
        long b48 = read();
        long b40 = read();
        long b32 = read();
        long b24 = read();
        long b16 = read();
        long b8 = read();
        return ((b64 << 56) + (b56 << 48) + (b48 << 40) + (b40 << 32) + (b32 << 24) + (b24 << 16) + (b16 << 8) + b8);
    }
    /**
     * Reads a byte from the stream.
     */
    public int readChar() throws IOException {
        if (_chunkLength > 0) {
            _chunkLength--;
            if (_chunkLength == 0 && _isLastChunk)
                _chunkLength = END_OF_DATA;
            int ch = parseUTF8Char();
            return ch;
        } else if (_chunkLength == END_OF_DATA) {
            _chunkLength = 0;
            return -1;
        }
        int tag = read();
        switch (tag) {
        case 'N':
            return -1;
        case 'S':
        case 's':
        case 'X':
        case 'x':
            _isLastChunk = tag == 'S' || tag == 'X';
            _chunkLength = (read() << 8) + read();
            _chunkLength--;
            int value = parseUTF8Char();
            // special code so successive read byte won't
            // be read as a single object.
            if (_chunkLength == 0 && _isLastChunk)
                _chunkLength = END_OF_DATA;
            return value;
        default:
            throw new IOException("expected 'S' at " + (char) tag);
        }
    }
    /**
     * Reads a byte array from the stream.
     */
    public int readString(char[] buffer, int offset, int length) throws IOException {
        int readLength = 0;
        if (_chunkLength == END_OF_DATA) {
            _chunkLength = 0;
            return -1;
        } else if (_chunkLength == 0) {
            int tag = read();
            switch (tag) {
            case 'N':
                return -1;
            case 'S':
            case 's':
            case 'X':
            case 'x':
                _isLastChunk = tag == 'S' || tag == 'X';
                _chunkLength = (read() << 8) + read();
                break;
            default:
                throw new IOException("expected 'S' at " + (char) tag);
            }
        }
        while (length > 0) {
            if (_chunkLength > 0) {
                buffer[offset++] = (char) parseUTF8Char();
                _chunkLength--;
                length--;
                readLength++;
            } else if (_isLastChunk) {
                if (readLength == 0)
                    return -1;
                else {
                    _chunkLength = END_OF_DATA;
                    return readLength;
                }
            } else {
                int tag = read();
                switch (tag) {
                case 'S':
                case 's':
                case 'X':
                case 'x':
                    _isLastChunk = tag == 'S' || tag == 'X';
                    _chunkLength = (read() << 8) + read();
                    break;
                default:
                    throw new IOException("expected 'S' at " + (char) tag);
                }
            }
        }
        if (readLength == 0)
            return -1;
        else if (_chunkLength > 0 || !_isLastChunk)
            return readLength;
        else {
            _chunkLength = END_OF_DATA;
            return readLength;
        }
    }
    /**
     * Reads a string
     *
     * <pre>
     * S b16 b8 string value
     * </pre>
     */
    public String readString() throws IOException {
        int tag = read();
        switch (tag) {
        case 'N':
            return null;
        case 'I':
            return String.valueOf(parseInt());
        case 'L':
            return String.valueOf(parseLong());
        case 'D':
            return String.valueOf(parseDouble());
        case 'S':
        case 's':
        case 'X':
        case 'x':
            _isLastChunk = tag == 'S' || tag == 'X';
            _chunkLength = (read() << 8) + read();
            _sbuf.setLength(0);
            int ch;
            while ((ch = parseChar()) >= 0)
                _sbuf.append((char) ch);
            return _sbuf.toString();
        default:
            throw expect("string", tag);
        }
    }
    /**
     * Reads an XML node.
     *
     * <pre>
     * S b16 b8 string value
     * </pre>
     */
    public org.w3c.dom.Node readNode() throws IOException {
        int tag = read();
        switch (tag) {
        case 'N':
            return null;
        case 'S':
        case 's':
        case 'X':
        case 'x':
            _isLastChunk = tag == 'S' || tag == 'X';
            _chunkLength = (read() << 8) + read();
            throw error("Can't handle string in this context");
        default:
            throw expect("string", tag);
        }
    }
    /**
     * Reads a byte array
     *
     * <pre>
     * B b16 b8 data value
     * </pre>
     */
    public byte[] readBytes() throws IOException {
        int tag = read();
        switch (tag) {
        case 'N':
            return null;
        case 'B':
        case 'b':
            _isLastChunk = tag == 'B';
            _chunkLength = (read() << 8) + read();
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            int data;
            while ((data = parseByte()) >= 0)
                bos.write(data);
            return bos.toByteArray();
        default:
            throw expect("bytes", tag);
        }
    }
    /**
     * Reads a byte from the stream.
     */
    public int readByte() throws IOException {
        if (_chunkLength > 0) {
            _chunkLength--;
            if (_chunkLength == 0 && _isLastChunk)
                _chunkLength = END_OF_DATA;
            return read();
        } else if (_chunkLength == END_OF_DATA) {
            _chunkLength = 0;
            return -1;
        }
        int tag = read();
        switch (tag) {
        case 'N':
            return -1;
        case 'B':
        case 'b':
            _isLastChunk = tag == 'B';
            _chunkLength = (read() << 8) + read();
            int value = parseByte();
            // special code so successive read byte won't
            // be read as a single object.
            if (_chunkLength == 0 && _isLastChunk)
                _chunkLength = END_OF_DATA;
            return value;
        default:
            throw new IOException("expected 'B' at " + (char) tag);
        }
    }
    /**
     * Reads a byte array from the stream.
     */
    public int readBytes(byte[] buffer, int offset, int length) throws IOException {
        int readLength = 0;
        if (_chunkLength == END_OF_DATA) {
            _chunkLength = 0;
            return -1;
        } else if (_chunkLength == 0) {
            int tag = read();
            switch (tag) {
            case 'N':
                return -1;
            case 'B':
            case 'b':
                _isLastChunk = tag == 'B';
                _chunkLength = (read() << 8) + read();
                break;
            default:
                throw new IOException("expected 'B' at " + (char) tag);
            }
        }
        while (length > 0) {
            if (_chunkLength > 0) {
                buffer[offset++] = (byte) read();
                _chunkLength--;
                length--;
                readLength++;
            } else if (_isLastChunk) {
                if (readLength == 0)
                    return -1;
                else {
                    _chunkLength = END_OF_DATA;
                    return readLength;
                }
            } else {
                int tag = read();
                switch (tag) {
                case 'B':
                case 'b':
                    _isLastChunk = tag == 'B';
                    _chunkLength = (read() << 8) + read();
                    break;
                default:
                    throw new IOException("expected 'B' at " + (char) tag);
                }
            }
        }
        if (readLength == 0)
            return -1;
        else if (_chunkLength > 0 || !_isLastChunk)
            return readLength;
        else {
            _chunkLength = END_OF_DATA;
            return readLength;
        }
    }
    /**
     * Reads a fault.
     */
    private HashMap<Object, Object> readFault() throws IOException {
        HashMap<Object, Object> map = new HashMap<Object, Object>();
        int code = read();
        for (; code > 0 && code != 'z'; code = read()) {
            _peek = code;
            Object key = readObject();
            Object value = readObject();
            if (key != null && value != null)
                map.put(key, value);
        }
        if (code != 'z')
            throw expect("fault", code);
        return map;
    }
    /**
     * Reads an object from the input stream with an expected type.
     */
    public Object readObject(Class<?> cl) throws IOException {
        if (cl == null || cl == Object.class)
            return readObject();
        int tag = read();
        switch (tag) {
        case 'N':
            return null;
        case 'M': {
            String type = readType();
            // hessian/3386
            if ("".equals(type)) {
                Deserializer reader;
                reader = _serializerFactory.getDeserializer(cl);
                return reader.readMap(this);
            } else {
                Deserializer reader;
                reader = _serializerFactory.getObjectDeserializer(type);
                return reader.readMap(this);
            }
        }
        case 'V': {
            String type = readType();
            int length = readLength();
            Deserializer reader;
            reader = _serializerFactory.getObjectDeserializer(type);
            if (cl != reader.getType() && cl.isAssignableFrom(reader.getType()))
                return reader.readList(this, length);
            reader = _serializerFactory.getDeserializer(cl);
            Object v = reader.readList(this, length);
            return v;
        }
        case 'R': {
            int ref = parseInt();
            return _refs.get(ref);
        }
        case 'r': {
            String type = readType();
            String url = readString();
            return resolveRemote(type, url);
        }
        }
        _peek = tag;
        // hessian/332i vs hessian/3406
        //return readObject();
        Object value = _serializerFactory.getDeserializer(cl).readObject(this);
        return value;
    }
    /**
     * Reads an arbitrary object from the input stream when the type
     * is unknown.
     */
    public Object readObject() throws IOException {
        int tag = read();
        switch (tag) {
        case 'N':
            return null;
        case 'T':
            return Boolean.valueOf(true);
        case 'F':
            return Boolean.valueOf(false);
        case 'I':
            return Integer.valueOf(parseInt());
        case 'L':
            return Long.valueOf(parseLong());
        case 'D':
            return Double.valueOf(parseDouble());
        case 'd':
            return new Date(parseLong());
        case 'x':
        case 'X': {
            _isLastChunk = tag == 'X';
            _chunkLength = (read() << 8) + read();
            return parseXML();
        }
        case 's':
        case 'S': {
            _isLastChunk = tag == 'S';
            _chunkLength = (read() << 8) + read();
            int data;
            _sbuf.setLength(0);
            while ((data = parseChar()) >= 0)
                _sbuf.append((char) data);
            return _sbuf.toString();
        }
        case 'b':
        case 'B': {
            _isLastChunk = tag == 'B';
            _chunkLength = (read() << 8) + read();
            int data;
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            while ((data = parseByte()) >= 0)
                bos.write(data);
            return bos.toByteArray();
        }
        case 'V': {
            String type = readType();
            int length = readLength();
            return _serializerFactory.readList(this, length, type);
        }
        case 'M': {
            String type = readType();
            return _serializerFactory.readMap(this, type);
        }
        case 'R': {
            int ref = parseInt();
            return _refs.get(ref);
        }
        case 'r': {
            String type = readType();
            String url = readString();
            return resolveRemote(type, url);
        }
        default:
            throw error("unknown code for readObject at " + codeName(tag));
        }
    }
    /**
     * Reads a remote object.
     */
    public Object readRemote() throws IOException {
        String type = readType();
        String url = readString();
        return resolveRemote(type, url);
    }
    /**
     * Reads a reference.
     */
    public Object readRef() throws IOException {
        return _refs.get(parseInt());
    }
    /**
     * Reads the start of a list.
     */
    public int readListStart() throws IOException {
        return read();
    }
    /**
     * Reads the start of a list.
     */
    public int readMapStart() throws IOException {
        return read();
    }
    /**
     * Returns true if this is the end of a list or a map.
     */
    public boolean isEnd() throws IOException {
        int code = read();
        _peek = code;
        return (code < 0 || code == 'z');
    }
    /**
     * Reads the end byte.
     */
    public void readEnd() throws IOException {
        int code = read();
        if (code != 'z')
            throw error("unknown code at " + codeName(code));
    }
    /**
     * Reads the end byte.
     */
    public void readMapEnd() throws IOException {
        int code = read();
        if (code != 'z')
            throw error("expected end of map ('z') at " + codeName(code));
    }
    /**
     * Reads the end byte.
     */
    public void readListEnd() throws IOException {
        int code = read();
        if (code != 'z')
            throw error("expected end of list ('z') at " + codeName(code));
    }
    /**
     * Adds a list/map reference.
     */
    public int addRef(Object ref) {
        if (_refs == null)
            _refs = new ArrayList<Object>();
        _refs.add(ref);
        return _refs.size() - 1;
    }
    /**
     * Adds a list/map reference.
     */
    public void setRef(int i, Object ref) {
        _refs.set(i, ref);
    }
    /**
     * Resets the references for streaming.
     */
    public void resetReferences() {
        if (_refs != null)
            _refs.clear();
    }
    /**
     * Resolves a remote object.
     */
    public Object resolveRemote(String type, String url) throws IOException {
        HessianRemoteResolver resolver = getRemoteResolver();
        if (resolver != null)
            return resolver.lookup(type, url);
        else
            return new HessianRemote(type, url);
    }
    /**
     * Parses a type from the stream.
     *
     * <pre>
     * t b16 b8
     * </pre>
     */
    public String readType() throws IOException {
        int code = read();
        if (code != 't') {
            _peek = code;
            return "";
        }
        _isLastChunk = true;
        _chunkLength = (read() << 8) + read();
        _sbuf.setLength(0);
        int ch;
        while ((ch = parseChar()) >= 0)
            _sbuf.append((char) ch);
        return _sbuf.toString();
    }
    /**
     * Parses the length for an array
     *
     * <pre>
     * l b32 b24 b16 b8
     * </pre>
     */
    public int readLength() throws IOException {
        int code = read();
        if (code != 'l') {
            _peek = code;
            return -1;
        }
        return parseInt();
    }
    /**
     * Parses a 32-bit integer value from the stream.
     *
     * <pre>
     * b32 b24 b16 b8
     * </pre>
     */
    private int parseInt() throws IOException {
        int b32 = read();
        int b24 = read();
        int b16 = read();
        int b8 = read();
        return (b32 << 24) + (b24 << 16) + (b16 << 8) + b8;
    }
    /**
     * Parses a 64-bit long value from the stream.
     *
     * <pre>
     * b64 b56 b48 b40 b32 b24 b16 b8
     * </pre>
     */
    private long parseLong() throws IOException {
        long b64 = read();
        long b56 = read();
        long b48 = read();
        long b40 = read();
        long b32 = read();
        long b24 = read();
        long b16 = read();
        long b8 = read();
        return ((b64 << 56) + (b56 << 48) + (b48 << 40) + (b40 << 32) + (b32 << 24) + (b24 << 16) + (b16 << 8) + b8);
    }
    /**
     * Parses a 64-bit double value from the stream.
     *
     * <pre>
     * b64 b56 b48 b40 b32 b24 b16 b8
     * </pre>
     */
    private double parseDouble() throws IOException {
        long b64 = read();
        long b56 = read();
        long b48 = read();
        long b40 = read();
        long b32 = read();
        long b24 = read();
        long b16 = read();
        long b8 = read();
        long bits = ((b64 << 56) + (b56 << 48) + (b48 << 40) + (b40 << 32) + (b32 << 24) + (b24 << 16) + (b16 << 8) + b8);
        return Double.longBitsToDouble(bits);
    }
    org.w3c.dom.Node parseXML() throws IOException {
        throw new UnsupportedOperationException();
    }
    /**
     * Reads a character from the underlying stream.
     */
    private int parseChar() throws IOException {
        while (_chunkLength <= 0) {
            if (_isLastChunk)
                return -1;
            int code = read();
            switch (code) {
            case 's':
            case 'x':
                _isLastChunk = false;
                _chunkLength = (read() << 8) + read();
                break;
            case 'S':
            case 'X':
                _isLastChunk = true;
                _chunkLength = (read() << 8) + read();
                break;
            default:
                throw expect("string", code);
            }
        }
        _chunkLength--;
        return parseUTF8Char();
    }
    /**
     * Parses a single UTF8 character.
     */
    private int parseUTF8Char() throws IOException {
        int ch = read();
        if (ch < 0x80)
            return ch;
        else if ((ch & 0xe0) == 0xc0) {
            int ch1 = read();
            int v = ((ch & 0x1f) << 6) + (ch1 & 0x3f);
            return v;
        } else if ((ch & 0xf0) == 0xe0) {
            int ch1 = read();
            int ch2 = read();
            int v = ((ch & 0x0f) << 12) + ((ch1 & 0x3f) << 6) + (ch2 & 0x3f);
            return v;
        } else
            throw error("bad utf-8 encoding at " + codeName(ch));
    }
    /**
     * Reads a byte from the underlying stream.
     */
    private int parseByte() throws IOException {
        while (_chunkLength <= 0) {
            if (_isLastChunk) {
                return -1;
            }
            int code = read();
            switch (code) {
            case 'b':
                _isLastChunk = false;
                _chunkLength = (read() << 8) + read();
                break;
            case 'B':
                _isLastChunk = true;
                _chunkLength = (read() << 8) + read();
                break;
            default:
                throw expect("byte[]", code);
            }
        }
        _chunkLength--;
        return read();
    }
    /**
     * Reads bytes based on an input stream.
     */
    public InputStream readInputStream() throws IOException {
        int tag = read();
        switch (tag) {
        case 'N':
            return null;
        case 'B':
        case 'b':
            _isLastChunk = tag == 'B';
            _chunkLength = (read() << 8) + read();
            break;
        default:
            throw expect("inputStream", tag);
        }
        return new InputStream() {
            boolean _isClosed = false;
            public int read() throws IOException {
                if (_isClosed || _is == null)
                    return -1;
                int ch = parseByte();
                if (ch < 0)
                    _isClosed = true;
                return ch;
            }
            public int read(byte[] buffer, int offset, int length) throws IOException {
                if (_isClosed || _is == null)
                    return -1;
                int len = HessianInput.this.read(buffer, offset, length);
                if (len < 0)
                    _isClosed = true;
                return len;
            }
            public void close() throws IOException {
                while (read() >= 0) {}
                _isClosed = true;
            }
        };
    }
    /**
     * Reads bytes from the underlying stream.
     */
    int read(byte[] buffer, int offset, int length) throws IOException {
        int readLength = 0;
        while (length > 0) {
            while (_chunkLength <= 0) {
                if (_isLastChunk)
                    return readLength == 0 ? -1 : readLength;
                int code = read();
                switch (code) {
                case 'b':
                    _isLastChunk = false;
                    _chunkLength = (read() << 8) + read();
                    break;
                case 'B':
                    _isLastChunk = true;
                    _chunkLength = (read() << 8) + read();
                    break;
                default:
                    throw expect("byte[]", code);
                }
            }
            int sublen = _chunkLength;
            if (length < sublen)
                sublen = length;
            sublen = _is.read(buffer, offset, sublen);
            offset += sublen;
            readLength += sublen;
            length -= sublen;
            _chunkLength -= sublen;
        }
        return readLength;
    }
    final int read() throws IOException {
        if (_peek >= 0) {
            int value = _peek;
            _peek = -1;
            return value;
        }
        int ch = _is.read();
        return ch;
    }
    public void close() {
        _is = null;
    }
    public Reader getReader() {
        return null;
    }
    protected IOException expect(String expect, int ch) {
        return error("expected " + expect + " at " + codeName(ch));
    }
    protected String codeName(int ch) {
        if (ch < 0)
            return "end of file";
        else
            return "0x" + Integer.toHexString(ch & 0xff) + " (" + (char) +ch + ")";
    }
    protected IOException error(String message) {
        if (_method != null)
            return new HessianProtocolException(_method + ": " + message);
        else
            return new HessianProtocolException(message);
    }
    static {
        try {
            _detailMessageField = Throwable.class.getDeclaredField("detailMessage");
            _detailMessageField.setAccessible(true);
        } catch (Throwable e) {}
    }
}
